﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Web;

namespace ER.Dynamic
{
    public static class ILGeneratorExtensions
    {
        /// <summary> 
        /// Puts the specified value on the stream of IL instructions. 
        /// </summary> 
        public static void PutValue(this ILGenerator il, object value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            var type = value.GetType();

            if (type == typeof(String))
                il.PutValue((String)value);

            else if (type == typeof(Boolean))
                il.PutValue((Boolean)value);

            else if (type == typeof(Byte))
                il.PutValue((Byte)value);

            else if (type == typeof(Int16))
                il.PutValue((Int16)value);

            else if (type == typeof(Int32))
                il.PutValue((Int32)value);

            else if (type == typeof(Int64))
                il.PutValue((Int64)value);

            else if (type == typeof(Single))
                il.PutValue((Single)value);

            else if (type == typeof(Double))
                il.PutValue((Double)value);

            else if (type == typeof(Decimal))
                il.PutValue((Decimal)value);

            else if (type == typeof(DateTime))
                il.PutValue((DateTime)value);

            else if (type == typeof(Guid))
                il.PutValue((Guid)value);

            else
                throw new NotSupportedException();
        }

        /// <summary> 
        /// Puts the specified string value on the stream of IL instructions. 
        /// </summary>  

        public static void PutValue(this ILGenerator il, String value)
        {
            il.Emit(OpCodes.Ldstr, value);
        }
        /// <summary> 
        /// Puts the specified boolean value on the stream of IL instructions. 
        /// </summary> 
        public static void PutValue(this ILGenerator il, Boolean value)
        {
            il.Emit(OpCodes.Ldc_I4, Convert.ToInt32(value));
        }
        /// <summary> 
        /// Puts the specified byte value on the stream of IL instructions. 
        /// </summary> 
        public static void PutValue(this ILGenerator il, Byte value)
        {
            il.Emit(OpCodes.Ldc_I4, Convert.ToInt32(value));
        }
        /// <summary> 
        /// Puts the specified int16 value on the stream of IL instructions. 
        /// </summary> 
        public static void PutValue(this ILGenerator il, Int16 value)
        {
            il.Emit(OpCodes.Ldc_I4, Convert.ToInt32(value));
        }
        /// <summary> 
        /// Puts the specified int32 value on the stream of IL instructions. 
        /// </summary> 
        public static void PutValue(this ILGenerator il, Int32 value)
        {
            il.Emit(OpCodes.Ldc_I4, value);
        }
        /// <summary> 
        /// Puts the specified int64 value on the stream of IL instructions. 
        /// </summary> 
        public static void PutValue(this ILGenerator il, Int64 value)
        {
            il.Emit(OpCodes.Ldc_I8, value);
        }
        /// <summary> 
        /// Puts the specified single value on the stream of IL instructions. 
        /// </summary> 
        public static void PutValue(this ILGenerator il, Single value)
        {
            il.Emit(OpCodes.Ldc_R4, value);
        }
        /// <summary> 
        /// Puts the specified double value on the stream of IL instructions. 
        /// </summary> 
        public static void PutValue(this ILGenerator il, Double value)
        {
            il.Emit(OpCodes.Ldc_R8, value);
        }

        private static readonly ConstructorInfo DecimalConstructorWithInt =
       typeof(Decimal).GetConstructor(new[] { typeof(Int32) });
        private static readonly ConstructorInfo DecimalConstructorWithLong =
       typeof(Decimal).GetConstructor(new[] { typeof(Int64) });
        private static readonly ConstructorInfo DecimalConstructor =
       typeof(Decimal).GetConstructor(new[] { typeof(Int32), typeof(Int32), typeof(Int32), 
typeof(Boolean), typeof(Byte) });


        /// <summary> 
        /// Puts the specified decimal value on the stream of IL instructions. 
        /// </summary> 
        public static void PutValue(this ILGenerator il, Decimal value)
        {
            var bits = decimal.GetBits(value);
            var exponent = unchecked(((bits[3] >> 16) & 0x1f));
            var sign = bits[3] < 0;

            if (exponent == 0 && bits[2] == 0)
            {
                if (bits[1] == 0 && (bits[0] > 0 || (bits[0] == 0 && !sign))) // fits in int32 
                {
                    il.Emit(OpCodes.Ldc_I4, sign ? -bits[0] : bits[0]);
                    il.Emit(OpCodes.Newobj, DecimalConstructorWithInt);
                    return;
                }
                if (bits[1] > 0) // fits in int64 
                {
                    var longValue = unchecked((long)(((ulong)(uint)bits[1] << 32) |
                   (uint)bits[0]));
                    il.Emit(OpCodes.Ldc_I8, sign ? -longValue : longValue);
                    il.Emit(OpCodes.Newobj, DecimalConstructorWithLong);
                    return;
                }
            }

            il.Emit(OpCodes.Ldc_I4, bits[0]);
            il.Emit(OpCodes.Ldc_I4, bits[1]);
            il.Emit(OpCodes.Ldc_I4, bits[2]);
            il.Emit(OpCodes.Ldc_I4, sign ? 1 : 0);
            il.Emit(OpCodes.Ldc_I4, exponent);
            il.Emit(OpCodes.Newobj, DecimalConstructor);
        }

        private static readonly ConstructorInfo DateTimeConstructor =
       typeof(DateTime).GetConstructor(new[] { typeof(Int64) });

        /// <summary> 
        /// Puts the specified datetime value on the stream of IL instructions. 
        /// </summary> 
        public static void PutValue(this ILGenerator il, DateTime value)
        {
            il.Emit(OpCodes.Ldc_I8, value.Ticks);
            il.Emit(OpCodes.Newobj, DateTimeConstructor);
        }

        private static readonly ConstructorInfo GuidConstructor =
       typeof(Guid).GetConstructor(new[] { typeof(byte[]) });

        /// <summary> 
        /// Puts the specified guid value on the stream of IL instructions. 
        /// </summary> 
        public static void PutValue(this ILGenerator il, Guid value)
        {
            il.PutArray(value.ToByteArray());
            il.Emit(OpCodes.Newobj, GuidConstructor);
        }

        /// <summary>  
        /// Puts the specified array value on the stream of IL instructions. 
        /// </summary> 
        public static void PutArray<T>(this ILGenerator il, IEnumerable<T> values)
        {
            if (values == null)
                throw new ArgumentNullException("values");

            var array = values.ToArray();

            il.Emit(OpCodes.Ldc_I4, array.Length);
            il.Emit(OpCodes.Newarr, typeof(T));

            for (var i = 0; i < array.Length; i++)
            {
                il.Emit(OpCodes.Dup);
                il.Emit(OpCodes.Ldc_I4, i);
                il.PutValue(array[i]);
                il.Emit(OpCodes.Stelem, typeof(T));
            }
        }

        public static void PutObject(this ILGenerator il, Type type)
        {
            ConstructorInfo typeConstructor;

            if (type.IsGenericType &&
           type.GetGenericArguments().OfType<TypeBuilder>().Any())
                typeConstructor = TypeBuilder.GetConstructor(type,
               type.GetGenericTypeDefinition().GetConstructor(Type.EmptyTypes));

            else
                typeConstructor = type.GetConstructor(Type.EmptyTypes);

            il.Emit(OpCodes.Newobj, typeConstructor);
        }
    }
}